package com.xqboss.chain.service;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.xqboss.chain.config.EChainConfig;
import com.xqboss.chain.domain.chain.SysUserChain;
import com.xqboss.chain.domain.chain.UpUpdateChain;
import com.xqboss.chain.domain.chain.UpUpdateChainLog;
import com.xqboss.chain.domain.chain.dto.EChainTxRetDto;
import com.xqboss.chain.domain.chain.dto.GetUpChainListDto;
import com.xqboss.chain.enums.UpChainStatusEnum;
import com.xqboss.chain.enums.UpChainTypeEnum;
import com.xqboss.chain.mapper.UpUpdateChainMapper;
import com.xqboss.common.core.redis.lock.LockValue;
import com.xqboss.common.core.redis.lock.RedisLock;
import com.xqboss.common.core.text.StrFormatter;
import com.xqboss.common.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.util.List;
import java.util.Objects;

/**
 * <p>
 * 藏品上链表 服务类
 * </p>
 *
 * @author ct
 * @since 2022-12-13
 */
@Service
@Slf4j
public class UpUpdateChainService extends ServiceImpl<UpUpdateChainMapper, UpUpdateChain> {

    final static String UPCHAINLOCK = "up_chain_lock";

    @Autowired
    private SysUserChainService userChainService;

    @Autowired
    private UpUpdateChainLogService upUpdateChainLogService;

    @Autowired
    private EChainService eChainService;

    @Autowired
    private EChainConfig eChainConfig;

    /**
     * 自动上链操作逻辑
     */
    public void autoUpChain(){
        PageHelper.startPage(1, 300);
        List<UpUpdateChain> list = list(new LambdaQueryWrapper<UpUpdateChain>()
                .select(UpUpdateChain::getId)
                .in(UpUpdateChain::getTxStatus, UpChainStatusEnum.NORMAL, UpChainStatusEnum.FAILED)
                .orderByAsc(UpUpdateChain::getCreateTime));
        for (UpUpdateChain upUpdateChain : list) {
            try{
                upchain(upUpdateChain.getId());
            }catch (Exception e){
                log.error("上链失败：{}", JSON.toJSONString(upUpdateChain), e);
            }
        }
    }

    //上链
    @RedisLock(lockKey = UPCHAINLOCK)
    public void upchain(@LockValue Long upUpdateChainId){
        UpUpdateChain updateChain = getById(upUpdateChainId);
        if(Objects.nonNull(updateChain)){
            if(updateChain.getTxStatus() == UpChainStatusEnum.NORMAL || updateChain.getTxStatus() == UpChainStatusEnum.FAILED){
                // 判断这个藏品之前是否有未完成上链记录？如果有就只能等待下次上链了！
                long count = count(new LambdaQueryWrapper<UpUpdateChain>()
                        .eq(UpUpdateChain::getNftId, updateChain.getNftId())
                        .eq(UpUpdateChain::getNftNumberId, updateChain.getNftNumberId())
                        .in(UpUpdateChain::getTxStatus, UpChainStatusEnum.NORMAL, UpChainStatusEnum.PENDING, UpChainStatusEnum.FAILED)
                        .lt(UpUpdateChain::getId, updateChain.getId()));
                if(count > 0){
                    throw new ServiceException(StrFormatter.format("藏品有上链失败未处理,上链ID：{}！", upUpdateChainId));
                }

                boolean update = update(new LambdaUpdateWrapper<UpUpdateChain>()
                        .set(UpUpdateChain::getTxStatus, UpChainStatusEnum.PENDING)
                        .eq(UpUpdateChain::getId, upUpdateChainId));
                if(!update){
                    throw new ServiceException("操作失败！");
                }
                // 获取接受者
                String toAddress;
                if(updateChain.getTxType() == UpChainTypeEnum.BURN){
                    toAddress = eChainConfig.getAddress();
                }else{
                    SysUserChain toUserChain = userChainService.getOrInitUserChain(updateChain.getToUserId());
                    toAddress = toUserChain.getAddress();
                }
                // 只有未上链或者上链失败的才能再次上链
                // 记录日志
                UpUpdateChainLog chainLog = new UpUpdateChainLog();
                chainLog.setUpUpdateChainId(upUpdateChainId);
                chainLog.setTxType(updateChain.getTxType());
                chainLog.setTxStatus(UpChainStatusEnum.PENDING);
                chainLog.setContract(updateChain.getContract());
                chainLog.setToAddress(toAddress);
                switch (updateChain.getTxType()){
                    case MINT:
                        try {
                            EChainTxRetDto resp = eChainService.safeMint(updateChain.getContract(), toAddress, BigInteger.valueOf(updateChain.getNumberValue()), null, null);
                            chainLog.setReceipt(resp.getResponse());
                            chainLog.setTxHash(resp.getHash());
                            chainLog.setReqNo(resp.getReqNo());
                            if(!resp.success()){
                                chainLog.setTxStatus(UpChainStatusEnum.FAILED);
                            }
                        } catch (Exception e) {
                            StringWriter stringWriter = new StringWriter();
                            PrintWriter writer = new PrintWriter(stringWriter);
                            e.printStackTrace(writer);
                            writer.flush();
                            writer.close();
                            chainLog.setTxStatus(UpChainStatusEnum.FAILED);
                            chainLog.setReceipt(stringWriter.toString());
                        }
                        break;
                    case TRANSACTION:
                        try {
                            // 判断当前藏品是否属于该用户？
                            SysUserChain fromUserChain = userChainService.getOrInitUserChain(updateChain.getFromUserId());
                            chainLog.setFromAddress(fromUserChain.getAddress());
                            EChainTxRetDto resp = eChainService.safeTransfer(updateChain.getContract(), fromUserChain.getPrivateKey(), fromUserChain.getAddress(), toAddress, BigInteger.valueOf(updateChain.getNumberValue()), null);
                            chainLog.setReceipt(resp.getResponse());
                            chainLog.setTxHash(resp.getHash());
                            chainLog.setReqNo(resp.getReqNo());
                            if(!resp.success()){
                                chainLog.setTxStatus(UpChainStatusEnum.FAILED);
                            }
                        } catch (Exception e) {
                            StringWriter stringWriter = new StringWriter();
                            PrintWriter writer = new PrintWriter(stringWriter);
                            e.printStackTrace(writer);
                            writer.flush();
                            writer.close();
                            chainLog.setTxStatus(UpChainStatusEnum.FAILED);
                            chainLog.setReceipt(stringWriter.toString());
                        }
                        break;
                    case BURN:
                        try {
                            // 判断当前藏品是否属于该用户？
                            EChainTxRetDto resp;
                            if(Objects.isNull(updateChain.getFromUserId())){
                                resp = eChainService.safeMint(updateChain.getContract(), eChainConfig.getAddress(), BigInteger.valueOf(updateChain.getNumberValue()), null, null);
                            }else{
                                SysUserChain fromUserChain = userChainService.getOrInitUserChain(updateChain.getFromUserId());
                                chainLog.setFromAddress(fromUserChain.getAddress());
                                resp = eChainService.safeTransfer(updateChain.getContract(), fromUserChain.getPrivateKey(), fromUserChain.getAddress(), eChainConfig.getAddress(), BigInteger.valueOf(updateChain.getNumberValue()), null);
                            }
                            chainLog.setReceipt(resp.getResponse());
                            chainLog.setTxHash(resp.getHash());
                            chainLog.setReqNo(resp.getReqNo());
                            if(!resp.success()){
                                chainLog.setTxStatus(UpChainStatusEnum.FAILED);
                            }
                        } catch (Exception e) {
                            StringWriter stringWriter = new StringWriter();
                            PrintWriter writer = new PrintWriter(stringWriter);
                            e.printStackTrace(writer);
                            writer.flush();
                            writer.close();
                            chainLog.setTxStatus(UpChainStatusEnum.FAILED);
                            chainLog.setReceipt(stringWriter.toString());
                        }
                        break;
                }
                // 存储日志
                upUpdateChainLogService.save(chainLog);
                if(chainLog.getTxStatus() != UpChainStatusEnum.PENDING){
                    update(new LambdaUpdateWrapper<UpUpdateChain>()
                            .set(UpUpdateChain::getTxStatus, chainLog.getTxStatus())
                            .eq(UpUpdateChain::getId, upUpdateChainId));
                }
            }
        }
    }

    @RedisLock(lockKey = UPCHAINLOCK)
    public void updateStatus(@LockValue Long upUpdateChainId, UpChainStatusEnum status) {
        boolean update = update(new LambdaUpdateWrapper<UpUpdateChain>()
                .set(UpUpdateChain::getTxStatus, status)
                .eq(UpUpdateChain::getId, upUpdateChainId));
        if(!update){
            throw new ServiceException("操作失败！");
        }
    }

    /**
     * 添加上链
     */
    public void addData(Long userNftId, String contract, Long nftId, Long nftNumberId, Long numberValue, Integer nftType, Long fromUserId, Long toUserId, UpChainTypeEnum txType) {
        UpUpdateChain upUpdateChain = new UpUpdateChain();
        upUpdateChain.setUserNftId(userNftId);
        upUpdateChain.setContract(contract);
        upUpdateChain.setNftId(nftId);
        upUpdateChain.setNftNumberId(nftNumberId);
        upUpdateChain.setNumberValue(numberValue);
        upUpdateChain.setNftType(nftType);
        upUpdateChain.setFromUserId(fromUserId);
        upUpdateChain.setToUserId(toUserId);
        upUpdateChain.setTxType(txType);
        upUpdateChain.setTxStatus(UpChainStatusEnum.NORMAL);
        save(upUpdateChain);
    }

    public List<UpUpdateChain> list(GetUpChainListDto params) {
        List<UpUpdateChain> list = baseMapper.getList(params);
        return list;
    }

    /**
     * 同步交易
     * @param id
     */
    public void syncTx(Long id) {

    }
}
